package basics.io.chatroom;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * redis 原理简单理解
 *
 * @author or2
 * @Description nio模型, 多人聊天室 - 服务器端
 * @create 2021年09月17日 时间: 17:43
 */
public class ChatServer {

    /**
     * 默认缓冲区大小
     */
    public static final int BYTE_CAPACITY = 1024;
    public static final String EXIT = "exit";

    /**
     * 核心 - 通道选择器
     */
    private Selector selector;
    /**
     * 字节缓冲区
     */
    private final ByteBuffer rBuffer = ByteBuffer.allocate(BYTE_CAPACITY);
    private final ByteBuffer wBuffer = ByteBuffer.allocate(BYTE_CAPACITY);
    /**
     * 统一编码
     */
    private final Charset charset = StandardCharsets.UTF_8;
    /**
     * 监听端口 - 默认 8888
     */
    private final int port;

    /**
     * 自定义监听端口
     *
     * @param port 端口号
     */
    public ChatServer(int port) {
        this.port = port;
    }

    /**
     * 启动服务器
     */
    public void start() {
        try (ServerSocketChannel socketChannel = ServerSocketChannel.open()) {
            /*非阻塞*/
            socketChannel.configureBlocking(false);
            /*绑定端口*/
            socketChannel.socket().bind(new InetSocketAddress(port));

            selector = Selector.open();
            /*监听 连接事件*/
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("服务器开始启动... 监听端口: " + port);

            while (true) {
                selector.select();
                /*处理监听到的事件*/
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectedKey : selectionKeys) {
                    handle(selectedKey);
                }
                selectionKeys.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 处理 选择器监听的事件
     * @param key 事件
     * @throws IOException
     */
    private void handle(SelectionKey key) throws IOException {
//        连接建立事件
        if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            client.register(selector, SelectionKey.OP_READ);
            Logger.getGlobal().info("客户端[" + client.socket().getPort() + "]建立连接");
            /*读取到信息*/
        } else if (key.isReadable()) {
            SocketChannel client = (SocketChannel) key.channel();
//            信息读取
            String msg = receive(client);
            if (msg.isEmpty()) {
                /*出现异常, 取消监听*/
                key.cancel();
                /*好习惯*/
                selector.wakeup();
            } else {
//                转发消息
                forwardMsg(client, msg);
                /*检测是否退出*/
                if (exit(msg)) {
                    key.cancel();
                    selector.wakeup();
                    Logger.getGlobal().info("客户端[" + client.socket().getPort() + "]断开连接");
                }
            }
        }
    }

    /**
     * 客户端退出
     * @param msg 根据消息内容判断
     * @return 是否退出?
     */
    private boolean exit(String msg) {
        return msg.equals(EXIT);
    }

    /**
     * 转发消息
     * @param client 发送者
     * @param msg 转发的内容
     * @throws IOException
     */
    private void forwardMsg(SocketChannel client, String msg) throws IOException {
        for (SelectionKey key : selector.keys()) {
            Channel channel = key.channel();
            if (channel instanceof ServerSocketChannel) {
                continue;
            }

            if (key.isValid() && !client.equals(channel)) {
                wBuffer.clear();
                wBuffer.put(charset.encode("客户端[" + client.socket().getPort() + "]: " + msg));
                wBuffer.flip();
                while (wBuffer.hasRemaining()) {
                    ((SocketChannel) channel).write(wBuffer);
                }
            }
        }
    }

    /**
     * 从通道中读取数据
     *
     * @param client 客户端通道
     * @return 读取到的字符串
     * @throws IOException
     */
    private String receive(SocketChannel client) throws IOException {
        rBuffer.clear();
        while (client.read(rBuffer) > 0) ;
        rBuffer.flip();
        return String.valueOf(charset.decode(rBuffer));
    }
}
